package com.jappydb;

import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateSequenceModel;

/**
 * An array is simply a JObject with ordered keys. The problem in dealing with
 * array.
 * 
 * @author harsh
 */
public class JArray extends JObject implements TemplateSequenceModel {

	/**
	 * Initialize a JArray with key, parent, ctxt.
	 * 
	 * @param key
	 * @param parent
	 * @param ctxt
	 */
	public JArray(JContext ctxt) {
		super(ctxt);
		mKeys = new CopyOnWriteArrayList<String>();
	}
	
	public JArray(JContext ctxt, JSONArray array) throws JSONException {
		this(ctxt);
		for (int i = 0; i < array.length(); ++i) {
			// Now it gets tricky
			if (array.optJSONObject(i) != null) {
				JSONObject item = array.getJSONObject(i);
				Object o = item.get(JConstants.VALUE);
				if (item.has(JConstants.KEY)) {
					put(item.getString(JConstants.KEY), 
							JUtils.objectToJ(ctxt, o));
				} else {
					add(JUtils.objectToJ(ctxt, item));
				}
			} else {
				add(JUtils.objectToJ(ctxt, array.get(i)));
			}
		}
	}

	/**
	 * Add an empty item.
	 * 
	 * @return
	 */
	public JObject addObject() {
		JObject o = new JObject(mCtxt);
		add(o);
		return o;
	}
	
	
	/**
	 * I believe this would just call add after generating an id.
	 * 
	 * @param o
	 */
	public String add(String value) {
		String key = mCtxt.getId();
		mKeys.add(key);
		super.put(key, value);
		return key;
	}
	
	public String add(JObject o) {
		String key = mCtxt.getId();
		mKeys.add(key);
		super.put(key, o);
		return key;
	}
	
	public String add(JArray o) {
		String key = mCtxt.getId();
		mKeys.add(key);
		super.put(key, o);
		return key;
	}
	
	public String add(Object value) {
		if (value instanceof String) {
			return add((String) value);
		} else if (value instanceof JArray) {
			return add((JArray) value);
		} else if (value instanceof JObject) {
			return add((JObject) value);
		} else {
			throw new IllegalArgumentException("Must be either String, JObject or JArray");
		}
	}

	public TemplateModel get(int k) throws TemplateModelException {
		return get(mKeys.get(k));
	}

	private void ensureKeyExists(String key) {
		if (key.indexOf('.') >= 0)
			throw new IllegalArgumentException(
					"Key contains dot. Please sanitize the key");
		key = key.toLowerCase();
		if (!mKeys.contains(key)) {
			mKeys.add(key);
		}
	}
	
	/**
	 * Putting into an array changes the semantics.
	 */
	public void put(String key, String value) {
		ensureKeyExists(key);
		super.put(key, value);
	}
	
	/**
	 * Putting into an array changes the semantics.
	 */
	public void put(String key, JObject value) {
		ensureKeyExists(key);
		super.put(key, value);
	}
	
	/**
	 * Putting into an array changes the semantics.
	 */
	public void put(String key, JArray value) {
		ensureKeyExists(key);
		super.put(key, value);
	}
	
	public void put(String key, Object value) {
		ensureKeyExists(key);
		super.put(key, value);
	}

	public int size() {
		return mKeys.size();
	}

	public JSONArray toJSONArray() throws JSONException {
		JSONArray ret = new JSONArray();
		Iterator<String> keys = mKeys.iterator();
		while (keys.hasNext()) {
			String key = keys.next();
			JSONObject item = new JSONObject();
			item.put(JConstants.KEY, key);
			
			Object value = mO.get(key);
			if (value instanceof JObject) {
				item.put(JConstants.VALUE, ((JObject) value).toJSONObject());
			} else if (value instanceof JArray) {
				item.put(JConstants.VALUE, ((JArray) value).toJSONArray());
			} else {
				item.put(JConstants.VALUE, value.toString());
			}
			ret.put(item);
		}
		return ret;
	}
	
	/**
	 * We need to delete the key from the list to do this.
	 */
	protected void deleteDirect(String key) {
		super.deleteDirect(key);
		mKeys.remove(key);
	}
	
	public String getKey(int k) {
		return mKeys.get(k);
	}
	
	public String getString(int k, String defValue) {
		return getString(getKey(k), defValue);
	}
	
	public JObject getJObject(int k) {
		return getJObject(mKeys.get(k));
	}
	
	public JArray getJArray(int k) {
		return getJArray(getKey(k));
	}
	
	public boolean hasString(int k) {
		return hasString(getKey(k));
	}
	
	public boolean hasJObject(int k) {
		return hasJObject(mKeys.get(k));
	}
	
	public boolean hasJArray(int k) {
		return hasJArray(getKey(k));
	}

	/**
	 * A List of keys we have.
	 */
	private CopyOnWriteArrayList<String> mKeys;
}
